﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using ParserGenerator.Extensions;

namespace ParserGenerator.Collections
{
    [Serializable,
     DebuggerDisplay(DebugDisplay),
     DebuggerTypeProxy(typeof(ReadOnlyLinkedList<>.DebugView))]
    public class ReadOnlyLinkedList<T> : IReadOnlyCollection<T>, ICollection<T>, ICollection
    {
        #region mutable list details
        private readonly LinkedList<T> _list;
        protected LinkedList<T> Items => _list;
        public int Count => _list.Count;

        public ReadOnlyLinkedList(LinkedList<T> list)
        {
            if (list == null)
                throw new ArgumentNullException(nameof(list));
            _list = list;
        }
        #endregion

        #region readonly list details
        public bool Contains(T item)
        {
            return _list.Contains(item);
        }

        public void CopyTo(T[] array)
        {
            CheckTargetArray(array, 0, Count);
            CopyTo(array, 0);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            CheckTargetArray(array, arrayIndex, Count);
            foreach (T item in this)
                array[arrayIndex++] = item;
        }

        private void CopyTo(object[] array, int arrayIndex)
        {
            CheckTargetArray(array, arrayIndex, Count);
            foreach (T item in this)
                array[arrayIndex++] = item;
        }

        private void CopyTo(Array array, int arrayIndex)
        {
            CheckTargetArray(array, arrayIndex, Count);
            var itemArray = array as T[];
            if (itemArray != null)
            {
                CopyTo(itemArray, arrayIndex);
                return;
            }
            var objectArray = array as object[];
            if (objectArray != null)
            {
                try
                {
                    CopyTo(objectArray, arrayIndex);
                    return;
                }
                catch (ArrayTypeMismatchException)
                {
                    throw new ArgumentException(SR.InvalidArrayElementType, nameof(array));
                }
            }
            throw new ArgumentException(SR.InvalidArrayElementType, nameof(array));
        }

        public IEnumerator<T> GetEnumerator()
        {
            return _list.GetEnumerator();
        }

        public T[] ToArray()
        {
            if (Count == 0)
                return ArrayExtensions.GetEmpty<T>();
            T[] array = new T[Count];
            CopyTo(array, 0);
            return array;
        }

        public override string ToString()
        {
            return "[" + string.Join(", ", this) + "]";
        }

        private bool IsReadOnly => true;
        private bool IsSynchronized => false;

        [NonSerialized]
        private object _syncRoot;
        private object SyncRoot
        {
            get
            {
                if (_syncRoot == null)
                {
                    var c = _list as ICollection;
                    if (c != null)
                        _syncRoot = c.SyncRoot;
                    else
                        Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null);
                }
                return _syncRoot;
            }
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();

        void ICollection.CopyTo(Array array, int arrayIndex) => CopyTo(array, arrayIndex);
        int ICollection.Count => Count;
        bool ICollection.IsSynchronized => IsSynchronized;
        object ICollection.SyncRoot => SyncRoot;
        void ICollection<T>.Add(T item) { throw new InvalidOperationException(SR.CollectionReadOnly); }
        void ICollection<T>.Clear() { throw new InvalidOperationException(SR.CollectionReadOnly); }
        bool ICollection<T>.Contains(T item) => Contains(item);
        void ICollection<T>.CopyTo(T[] array, int arrayIndex) => CopyTo(array, arrayIndex);
        bool ICollection<T>.Remove(T item) { throw new InvalidOperationException(SR.CollectionReadOnly); }
        int ICollection<T>.Count => Count;
        bool ICollection<T>.IsReadOnly => IsReadOnly;
        int IReadOnlyCollection<T>.Count => Count;
        #endregion

        #region list comparison
        public int CompareTo(Sequence<T> other)
        {
            return IListExtensions.ListCompareTo(this, other);
        }

        public int CompareTo(object obj)
        {
            if (obj == null)
                return 1;
            if (obj is Sequence<T>)
                return CompareTo((Sequence<T>)obj);
            throw new ArgumentException(SR.ObjectsAreNotComparable);
        }

        public bool Equals(Sequence<T> other)
        {
            return IListExtensions.ListEquals(this, other);
        }

        public override bool Equals(object obj)
        {
            return obj is Sequence<T> && Equals((Sequence<T>)obj);
        }

        public override int GetHashCode()
        {
            return IListExtensions.GetListHashCode(this);
        }
        #endregion

        #region utility methods
        private static void CheckTargetArray(T[] array, int arrayIndex, int size)
        {
            if (array == null)
                throw new ArgumentNullException(nameof(array));
            if (arrayIndex < 0)
                throw new ArgumentOutOfRangeException(nameof(arrayIndex), SR.IndexMustBeNonNegative);
            if (size < 0)
                throw new ArgumentOutOfRangeException(nameof(size), SR.SizeMustBeNonNegative);
            if (arrayIndex > array.Length)
                throw new ArgumentOutOfRangeException(nameof(arrayIndex), SR.IndexOutOfRange);
            if (size > array.Length - arrayIndex)
                throw new ArgumentException(SR.TargetArrayTooSmall, nameof(array));
        }

        private static void CheckTargetArray(Array array, int arrayIndex, int size)
        {
            if (array == null)
                throw new ArgumentNullException(nameof(array));
            if (array.Rank != 1)
                throw new ArgumentException(SR.RankMultiDimNotSupported, nameof(array));
            if (array.GetLowerBound(0) != 0)
                throw new ArgumentException(SR.NonZeroLowerBound, nameof(array));
            if (arrayIndex < 0)
                throw new ArgumentOutOfRangeException(nameof(arrayIndex), SR.IndexMustBeNonNegative);
            if (size < 0)
                throw new ArgumentOutOfRangeException(nameof(size), SR.SizeMustBeNonNegative);
            if (arrayIndex > array.Length)
                throw new ArgumentOutOfRangeException(nameof(arrayIndex), SR.IndexOutOfRange);
            if (size > array.Length - arrayIndex)
                throw new ArgumentException(SR.TargetArrayTooSmall, nameof(array));
        }
        #endregion

        #region debug view
        private const string DebugDisplay = "Count = {Count}";

        [Serializable]
        private sealed class DebugView
        {
            private readonly ReadOnlyLinkedList<T> _collection;

            public DebugView(ReadOnlyLinkedList<T> collection)
            {
                if (collection == null)
                    throw new ArgumentNullException(nameof(collection));
                _collection = collection;
            }

            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
            public T[] Items
            {
                get
                {
                    T[] array = new T[_collection.Count];
                    _collection.CopyTo(array, 0);
                    return array;
                }
            }
        }
        #endregion
    }
}
